/*
 * MatrixBasis.java
 *
 * Created on June 28, 2004, 3:05 PM
 */

package com.milowski.monos.binomial;

import java.io.*;
import java.util.*;
import java.util.logging.*;
import java.util.regex.*;
import com.milowski.monos.*;
import com.milowski.monos.monomial.*;
import org.infoset.xml.Element;
import org.infoset.xml.InfosetFactory;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.XMLException;

/**
 *
 * @author  R. Alexander Milowski
 */
public class MatrixBasis implements XMLMarshaller {

   static Pattern whitespace = Pattern.compile("\\s+");
   
   static class ClassUnmarshaller implements XMLUnmarshaller {
      public Object fromXML(Element representation)
         throws XMLException
      {
         return MatrixBasis.fromXML(representation);
      }
   }
   public static void register()
      throws XMLException
   {
      XML.registerUnmarshaller(XML.matrixName,new ClassUnmarshaller());
   }

   LexOrder order;
   short [][]values;
   boolean compact;

   public MatrixBasis(int rowCount,int columnCount) {
      order = allocLexOrder(columnCount);
      allocMatrix(rowCount,columnCount);
      compact = true;
   }
   
   /** Creates a new instance of MatrixBasis */
   public MatrixBasis(LexOrder order,int rowCount, int columnCount) {
      this.order = order;
      allocMatrix(rowCount,columnCount);
      compact = true;
   }
   
   public MatrixBasis(int [][]basis) {
      order = allocLexOrder(basis[0].length);
      allocMatrix(basis.length,basis[0].length);
      compact = true;
      for (int row=0; row<basis.length; row++) {
         for (int col=0; col<basis[row].length; col++) {
            values[row][col] = (short)basis[row][col];
         }
      }
   }
   
   public boolean isCompact() {
      return compact;
   }
   
   public void setCompact(boolean flag) {
      compact = flag;
   }
   
   private void allocMatrix(int rowCount,int columnCount) {
		values = new short[rowCount][];
      for (int i=0;  i<values.length; i++) {
         values[i] = new short[columnCount];
      }
      
   }
   
   private static LexOrder allocLexOrder(int size)
   {
      LexOrder l = new LexOrder();
      for (int i=1; i<=size; i++) {
         l.add("x"+i);
      }
      return l;
   }
   
   public void setLexOrder(LexOrder newOrder) {
      if (order.size()!=newOrder.size()) {
         throw new IllegalArgumentException("The order size is not the same: "+order.size()+"!="+newOrder.size());
      }
      order = newOrder;
   }
   
   public int getRowCount() {
      return values.length;
   }
   
   public int getColumnCount() {
      return values[0].length;
   }
   
   public short get(int row,int column) {
      return values[row][column];
   }
   
   public void set(int row,int column,short value) {
      values[row][column] = value;
   }
   
   public void transpose() {
      int numColumns = values[0].length;
      short [][] newvalues = new short[numColumns][];
      for (int col=0; col<numColumns; col++) {
         newvalues[col] = new short[values.length];
         for (int row=0; row<values.length; row++) {
            newvalues[col][row] = values[row][col];
         }
      }
      values = newvalues;
   }
   
   public String rowToString(int row) {
      StringBuffer sb = new StringBuffer();
      for (int i=0; i<values[row].length; i++) {
         if (i!=0) {
            sb.append(' ');
         }
         sb.append(Short.toString(values[row][i]));
      }
      return sb.toString();
   }
   
   
   
   public static MatrixBasis read(Reader input) 
      throws java.io.IOException 
   {
      return read(input,false,null);
   }
   public static MatrixBasis read(Reader input,boolean reverse,int [] reorder) 
      throws java.io.IOException 
   {
      Logger log = Logger.getAnonymousLogger();
      boolean isFineLog = log.isLoggable(Level.FINE);
      BufferedReader breader = new BufferedReader(input);
      String spec = breader.readLine();
      if (isFineLog) {
         log.fine("Matrix spec line: "+spec);
      }
      if (spec==null) {
         throw new java.io.IOException("EOF before spec line was read.");
      }
      String [] values = spec.split("\\s");
      if (values.length>2) {
         throw new java.io.IOException("Row/Column spec line has too many values (length="+values.length+")");
      }
      int rowCount = Integer.parseInt(values[0]);
      int columnCount = Integer.parseInt(values[1]);
      MatrixBasis basis = new MatrixBasis(rowCount,columnCount);
      
      if (reverse) {
         reorder = new int[columnCount];
         for (int i=0; i<reorder.length; i++) {
            reorder[i] = reorder.length-i-1;
         }
      }
      
      for (int i=0; i<rowCount; i++) {
         String line = breader.readLine();
         if (isFineLog) {
            log.fine("Matrix input: "+line);
         }
         values = line.trim().split("\\s+");
         if (values.length!=columnCount) {
            throw new java.io.IOException("Bad data line at "+(i+2)+", length is "+values.length);
         }
         for (int j=0; j<values.length; j++) {
            
            short exp = Short.parseShort(values[j]);
            int index = reorder==null ? j : reorder[j];
            basis.set(i,index,exp);
            
         }
      }
      
      return basis;
      
   }
   
   public void write(OutputStream os)
      throws IOException
   {
      Writer out = new OutputStreamWriter(os);
      write(out);
      out.flush();
   }
   
   public void write(Writer out) 
      throws IOException
   {
      out.write(values.length+" "+values[0].length);
      out.write('\n');
      for (int row=0; row<values.length; row++) {
         for (int col=0; col<values[row].length; col++) {
            out.write(Short.toString(values[row][col]));
            out.write(' ');
         }
         out.write('\n');
      }
      out.flush();
   }
   
   public BinomialIdeal toBinomials(boolean compactBinomials) {
      if (!compact) {
         int size = values[0].length/2;
         LexOrder neworder = new LexOrder();
         for (int i=0; i<size; i++) {
            neworder.add(order.get(i));
         }
         BinomialIdeal list = new BinomialIdeal(neworder);
         if (compactBinomials) {
            for (int i=0; i<values.length; i++) {
               CompactBinomial b = new CompactBinomial(size);
               list.add(b);
            }
         } else {
            for (int i=0; i<values.length; i++) {
               FullBinomial b = new FullBinomial(size);
               short [] leading = b.getLeadingTerm();
               short [] trailing = b.getTrailingTerm();
               System.arraycopy(values[i],0, leading,0,leading.length);
               System.arraycopy(values[i],size, trailing,0,trailing.length);
               list.add(b);
            }
         }
         return list;
      } else {
         BinomialIdeal list = new BinomialIdeal(order);
         if (compactBinomials) {
            for (int i=0; i<values.length; i++) {
               list.add(new CompactBinomial(values[i]));
            }
         } else {
            for (int i=0; i<values.length; i++) {
               list.add(new FullBinomial(values[i]));
            }
         }
         return list;
      }
   }
   public BinomialIdeal toBinomials() {
      return toBinomials(compact);
   }
   
   public MonomialIdeal getInitialIdeal() {
      MonomialIdeal list = new MonomialIdeal(order);
      for (int i=0; i<values.length; i++) {
         Monomial m = new Monomial(values[i]);
         short [] mvalues = m.getValues();
         for (int v=0; v<mvalues.length; v++) {
            if (mvalues[v]<0) {
               mvalues[v] = 0;
            }
         }
         list.add(m);
      }
      return list;
   }
   
   public MonomialIdeal toMonomials() {
      MonomialIdeal list = new MonomialIdeal(order);
      for (int i=0; i<values.length; i++) {
         list.add(new Monomial(values[i]));
      }
      return list;
   }
   
   public static BinomialIdeal readMatrixAsBinomials(Reader input) 
      throws java.io.IOException
           
   {
      return readMatrixAsBinomials(input,false);
   }
   
   public static BinomialIdeal readMatrixAsBinomials(Reader input,boolean compact) 
      throws java.io.IOException
   {
      return readMatrixAsBinomials(input,compact,false,null);
   }
   
   public static BinomialIdeal readMatrixAsBinomials(Reader input,boolean compact,boolean reverse,int [] reorder) 
      throws java.io.IOException
   {
      BufferedReader breader = new BufferedReader(input);
      String spec = breader.readLine();
      String [] values = spec.split("\\s");
      if (values.length>2) {
         throw new java.io.IOException("Row/Column spec line has too many values (length="+values.length+")");
      }
      int rowCount = Integer.parseInt(values[0]);
      int columnCount = Integer.parseInt(values[1]);
      if (reverse) {
         reorder = new int[columnCount];
         for (int i=0; i<reorder.length; i++) {
            reorder[i] = reorder.length-i-1;
         }
      }
      int doubleColumnCount = 2*columnCount;
      LexOrder lorder = allocLexOrder(columnCount);
      BinomialIdeal list = new BinomialIdeal(lorder);
      
      for (int i=0; i<rowCount; i++) {
         String line = breader.readLine().trim();
         values = whitespace.split(line);
         if (values.length==columnCount) {
            if (compact) {
               CompactBinomial b = new CompactBinomial(lorder.size());
               short [] bvalues = b.getValues();
               for (int j=0; j<values.length; j++) {
                  int index = reorder==null ? j : reorder[j];
                  bvalues[index] = Short.parseShort(values[j]);
               }
               list.add(b);
            } else {
               FullBinomial b = new FullBinomial(lorder.size());
               short [] lvalues = b.getLeadingTerm();
               short [] tvalues = b.getTrailingTerm();
               for (int j=0; j<values.length; j++) {
                  short value = Short.parseShort(values[j]);
                  int index = reorder==null ? j : reorder[j];
                  if (value<0) {
                     lvalues[index] = 0;
                     tvalues[index] = (short)-value;
                  } else {
                     lvalues[index] = value;
                     tvalues[index] = 0;
                  }
               }
               list.add(b);
            }
         } else if (values.length==doubleColumnCount) {
            if (compact) {
               CompactBinomial b = new CompactBinomial(lorder.size());
               short [] bvalues = b.getValues();
               for (int j=0; j<values.length; j++) {
                  short value = Short.parseShort(values[j]);
                  if (value<0) {
                     throw new IOException("Negative values are not allowed in double-width format.");
                  }
                  int index = reorder==null ? j : reorder[j];
                  if (j>=columnCount) {
                     bvalues[index] = (short)-value;
                  } else {
                     bvalues[index] = value;
                  }
               }
               list.add(b);
            } else {
               FullBinomial b = new FullBinomial(lorder.size());
               short [] lvalues = b.getLeadingTerm();
               short [] tvalues = b.getTrailingTerm();
               for (int j=0; j<values.length; j++) {
                  short value = Short.parseShort(values[j]);
                  if (value<0) {
                     throw new IOException("Negative values are not allowed in double-width format.");
                  }
                  if (j>=columnCount) {
                     int index = reorder==null ? j-columnCount : reorder[j-columnCount];
                     tvalues[index] = value;
                  } else {
                     int index = reorder==null ? j : reorder[j];
                     lvalues[index] = value;
                  }
               }
               list.add(b);
            }
         } else {
            throw new IOException("Bad data line at "+(i+2)+", length is "+values.length);
         }
      }
      
      return list;
      
   }
   
   public static MatrixBasis fromXML(Element matrixElement) 
      throws XMLException
   {
      List<Element> rows = new ArrayList();
      for (Iterator<Element> elements = matrixElement.getElementChildren(); elements.hasNext(); ) {
         rows.add(elements.next());
      }
      
      int rowCount = rows.size();
      if (rowCount==0) {
         throw new XMLException("The matrix element cannot be empty.");
      }
      
      String compactValue = matrixElement.getAttributeValue("compact");
      boolean compact = compactValue==null ? true : compactValue.equals("true");

      int columnCount = -1;
      int row = 0;
      MatrixBasis matrix = null;
      for (Iterator rowsToProcess=rows.iterator(); rowsToProcess.hasNext(); ) {
         Element listElement = (Element)rowsToProcess.next();
         if (!listElement.getName().equals(XML.ilistName)) {
            throw new XMLException("Element "+listElement.getName()+" is not allowed in "+XML.matrixName);
         }
         String value = listElement.getText();
         String [] values = whitespace.split(value);
         if (columnCount<0) {
            columnCount = values.length ;
            matrix = new MatrixBasis(rowCount,columnCount);
            matrix.setCompact(compact);
         } else if (columnCount!=values.length) {
            throw new XMLException("Row "+row+" does not have "+columnCount+" entries in the list.");
         }
         for (int i=0; i<values.length; i++) {
            matrix.set(row,i,Short.parseShort(values[i]));
         }
         row++;
      }
      return matrix;
   }
   
   public void toXML(ItemDestination dest)
      throws XMLException
   {
      toXML(InfosetFactory.getDefaultInfoset().createItemConstructor(),dest);
   }

   public void toXML(ItemConstructor constructor,ItemDestination dest)
      throws XMLException
   {
      Element start = constructor.createElement(XML.matrixName);
      if (!compact) {
         start.setAttributeValue("compact","false");
      }
      dest.send(start);
      dest.send(constructor.createCharacters("\n"));
      int rowCount = getRowCount();
      for (int i=0; i<rowCount; i++) {
         dest.send(constructor.createElement(XML.ilistName));
         dest.send(constructor.createCharacters(rowToString(i)));
         dest.send(constructor.createElementEnd(XML.ilistName));
         dest.send(constructor.createCharacters("\n"));
      }
      dest.send(constructor.createElementEnd(XML.matrixName));      
      dest.send(constructor.createCharacters("\n"));
   }
   
   public boolean equals(Object other) {
      if (!(other instanceof MatrixBasis)) {
         return false;
      }
      MatrixBasis mother = (MatrixBasis)other;
      int otherCols = mother.getColumnCount();
      int otherRows = mother.getRowCount();
      if (otherCols!=getColumnCount() || otherRows!=getRowCount()) {
         return false;
      }
      for (int row=0; row<values.length; row++) {
         for (int col=0; col<values[row].length; col++) {
            if (values[row][col]!=mother.get(row,col)) {
               return false;
            }
         }
      }
      return true;
   }
}
